﻿using System.Collections.Concurrent;
using System.FileServer.Models;
using System.Security.Cryptography;
using System.Text;

using GCC.Container.Abstracts;
using GCC.Container.Models;

using GCCGrpc.FileServer;

using LuanNiao.JsonConverterExtends;

using Microsoft.AspNetCore.Http;

namespace System.FileServer
{
    /// <summary>
    /// 文件管理,文件的操作都在这里
    /// </summary>
    internal class FileManager
    {
        public static FileManager Instance = new();
        private DirectoryInfo? _saveDir = null;
        private string? _currentPointName = null;
        private AbsGCCLogger? _logger;



        private FileManager()
        {

        }

        /// <summary>
        /// 初始化
        /// </summary>
        public void Init(AbsGCCContext context, AbsGCCLoggerProvider logger)
        {
            _logger = logger.CreateLogger<FileManager>();
            this._currentPointName = context.PointName;
            this._saveDir = new DirectoryInfo(context.GetConfigByPath(new string[] { context.PointName ?? "", "FileLocalSavePath" }));
            if (!_saveDir.Exists)
            {
                _saveDir.Create();
            }
        }

        /// <summary>
        /// 保存文件
        /// </summary>
        /// <param name="rawFile"></param>
        public bool SaveFiles(IFormFileCollection rawFiles, out List<FileDBModel> fileInfos)
        {
            fileInfos = new List<FileDBModel>();
            if (this._saveDir is null)
            {
                _logger?.Error("save dir is empty, check your config it needs FileLocalSavePath");
                return false;
            }
            if (GCCEntrance.DBManager is null || GCCEntrance.DBManager.LocalDB is null)
            {
                _logger?.Error("Local db instance was null.");
                return false;
            }

            foreach (var rawFile in rawFiles)
            {
                var fileType = rawFile.FileName[rawFile.FileName.LastIndexOf('.')..];
                ///这里是为了让未来能做已经传输了就不传,但是最好不要,原因在于你需要向集群所有服务器发送数据来检查,要不然等于做了一半,
                ///另外,如果你检查了,那么不存储,本地要用的时候就凉了.
                ///如果只不存储本地的,那么删除就需要全库检索,做这个功能时请分清楚你们的诉求,在做.
                var fileCheckCode = SHA256Encrypt(rawFile.OpenReadStream());
                var fileInfo = new FileDBModel()
                {
                    OriginalName = rawFile.FileName,
                    MimeType = rawFile.ContentType,
                    Size = rawFile.Length,
                    Type = fileType,
                    CurrentPointName = this._currentPointName ?? "",
                    CheckCode = fileCheckCode
                };
                fileInfo.CurrentName += fileType;
                ///这里也不要动,如果服务一直开启,这句话就非常有意义了
                var fileFolder = Path.Combine(_saveDir.FullName, DateTime.Now.ToString("yyyy_MM_dd"));
                _ = Directory.CreateDirectory(fileFolder);
                var fileSavePath = Path.Combine(fileFolder, $"{fileInfo.CurrentName}");
                fileInfo.FileSavePath = fileSavePath;
                using var file = File.Open(fileSavePath, FileMode.CreateNew, FileAccess.ReadWrite);
                rawFile.CopyTo(file);
                file.Flush();
                file.Close();
                fileInfos.Add(fileInfo);
            }
            return GCCEntrance.DBManager.LocalDB.Insert(fileInfos).ExecuteAffrows() == rawFiles.Count;
        }


        /// <summary>
        /// 保存文件
        /// </summary>
        /// <param name="rawFile"></param>
        public bool SaveFiles(GRPCCreateFilesRequest rawFiles, out List<FileDBModel> fileInfos)
        {
            fileInfos = new List<FileDBModel>();
            if (this._saveDir is null)
            {
                _logger?.Error("save dir is empty, check your config it needs FileLocalSavePath");
                return false;
            }
            if (GCCEntrance.DBManager is null || GCCEntrance.DBManager.LocalDB is null)
            {
                _logger?.Error("Local db instance was null.");
                return false;
            }

            foreach (var rawFile in rawFiles.Items)
            {

                var fileType = rawFile.FileName[rawFile.FileName.LastIndexOf('.')..];

                var fileInfo = new FileDBModel()
                {
                    OriginalName = rawFile.FileName,
                    MimeType = rawFile.ContentType,
                    Type = fileType,
                    CurrentPointName = this._currentPointName ?? ""
                };
                fileInfo.CurrentName += fileType;
                ///这里也不要动,如果服务一直开启,这句话就非常有意义了
                var fileFolder = Path.Combine(_saveDir.FullName, DateTime.Now.ToString("yyyy_MM_dd"));
                _ = Directory.CreateDirectory(fileFolder);
                var fileSavePath = Path.Combine(fileFolder, $"{fileInfo.CurrentName}");
                fileInfo.FileSavePath = fileSavePath;


                using var file = File.Open(fileSavePath, FileMode.CreateNew, FileAccess.ReadWrite);

                rawFile.Content.WriteTo(file);
                var fileCheckCode = SHA256Encrypt(file);
                file.Flush();
                fileInfo.Size = file.Length;
                fileInfo.CheckCode = fileCheckCode;
                file.Close();
                fileInfos.Add(fileInfo);
            }
            return GCCEntrance.DBManager.LocalDB.Insert(fileInfos).ExecuteAffrows() == rawFiles.Items.Count;
        }

        private readonly ConcurrentDictionary<string, string> _allIndexFile = new();

        /// <summary>
        /// 保存分片文件
        /// </summary>
        /// <param name="request">请求</param>
        /// <param name="fileInfo">文件信息</param>
        /// <returns></returns>
        public bool SaveFile(GRPCCreateBigFileRequest request, out FileDBModel? fileInfo)
        {
            fileInfo = null;
            if (GCCEntrance.DBManager is null || GCCEntrance.DBManager.LocalDB is null)
            {
                _logger?.Error("DB context is null.");
                return false;
            }
            if (this._saveDir is null)
            {
                _logger?.Error("save dir is empty, check your config it needs FileLocalSavePath");
                return false;
            }
            var lockKey = _allIndexFile.GetOrAdd(request.FileName, name => Guid.NewGuid().ToString("N"));

            var lockFilePath = Path.Combine(_saveDir.FullName, request.Token + ".lock");
            while (true)
            {
                try
                {
                    if (File.Exists(lockFilePath))
                    {
                        Task.Delay(100).Wait();
                        continue;
                    }
                    File.Create(lockFilePath).Close();
                    break;
                }
                catch
                {
                    continue;
                }
            }


            lock (lockKey)
            {
                var tokenFilePath = Path.Combine(_saveDir.FullName, request.Token);
                var fileMetadataPath = Path.Combine(_saveDir.FullName, request.Token + ".md");
                var fileRawInfoPath = Path.Combine(_saveDir.FullName, request.Token + ".bin");
                var metadata = new Dictionary<string, string>();
                var list = new List<long>();
                {
                    using var tokenFile = File.Open(tokenFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);

                    if (tokenFile.Length != 0)
                    {
                        var data = new byte[tokenFile.Length];
                        _ = tokenFile.Read(data);
                        var info = Encoding.UTF8.GetString(data);
                        list = Text.Json.JsonSerializer.Deserialize<List<long>>(info, CommonSerializerOptions.CamelCaseChineseNameCaseInsensitive)!;
                    }
                    else
                    {
                        for (int i = 0; i < request.PackageCount; i++)
                        {
                            list.Add(i);
                        }
                    }
                    var index = list.FindIndex(item => item == request.ThisPackageIndex);
                    if (index != -1)
                    {
                        list.RemoveAt(index);
                    }
                    _ = tokenFile.Seek(0, SeekOrigin.Begin);
                    tokenFile.SetLength(0);
                    tokenFile.Flush();
                    tokenFile.Position = 0;
                    tokenFile.Write(Encoding.UTF8.GetBytes(Text.Json.JsonSerializer.Serialize(list, CommonSerializerOptions.CamelCaseChineseNameCaseInsensitive)));
                    tokenFile.Flush();
                    tokenFile.Close();
                }

                {
                    if (!File.Exists(fileMetadataPath))
                    {
                        metadata.Add("fileName", request.FileName);
                        metadata.Add("fileMime", request.ContentType);
                        using var fs = File.Open(fileMetadataPath, FileMode.CreateNew, FileAccess.Write);
                        fs.Write(Encoding.UTF8.GetBytes(Text.Json.JsonSerializer.Serialize(metadata, CommonSerializerOptions.CamelCaseChineseNameCaseInsensitive)));
                        fs.Flush();
                        fs.Close();
                    }
                    else
                    {
                        using var fs = File.OpenText(fileMetadataPath);
                        metadata = Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(fs.ReadToEnd(), CommonSerializerOptions.CamelCaseChineseNameCaseInsensitive)!;
                        fs.Close();
                    }
                }

                {
                    using var rawInfoFile = File.Open(fileRawInfoPath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
                    rawInfoFile.Position = request.OffSet;
                    var data = new byte[request.Content.Length];
                    using var ms = new MemoryStream(data);
                    request.Content.WriteTo(ms);
                    rawInfoFile.Write(data);
                    rawInfoFile.Flush();
                    rawInfoFile.Close();
                    rawInfoFile.Dispose();
                    ms.Close();
                }

                if (list.Count == 0)
                {
                    var fileName = metadata["fileName"];
                    var fileMime = metadata["fileMime"];
                    var fileType = fileName.LastIndexOf('.') == -1 ? fileName : fileName[fileName.LastIndexOf('.')..];

                    fileInfo = new FileDBModel()
                    {
                        OriginalName = fileName,
                        MimeType = fileMime,
                        Type = fileType,
                        CurrentPointName = this._currentPointName ?? ""
                    };
                    fileInfo.CurrentName += fileType;
                    ///这里也不要动,如果服务一直开启,这句话就非常有意义了
                    var fileFolder = Path.Combine(_saveDir.FullName, DateTime.Now.ToString("yyyy_MM_dd"));
                    _ = Directory.CreateDirectory(fileFolder);
                    var fileSavePath = Path.Combine(fileFolder, $"{fileInfo.CurrentName}");
                    fileInfo.FileSavePath = fileSavePath;


                    {
                        using var fileRawInfoFile = File.Open(fileRawInfoPath, FileMode.Open, FileAccess.ReadWrite);
                        fileInfo.Size = fileRawInfoFile.Length;
                        var fileCheckCode = SHA256Encrypt(fileRawInfoFile);
                        fileInfo.CheckCode = fileCheckCode;
                        fileRawInfoFile.Close();
                    }
                    File.Move(fileRawInfoPath, fileSavePath);
                    File.Delete(fileMetadataPath);
                    File.Delete(tokenFilePath);
                    _ = _allIndexFile.TryRemove(request.FileName, out var _);

                    _ = GCCEntrance.DBManager.LocalDB.Insert(fileInfo).ExecuteAffrows();
                }
                else
                {
                    fileInfo = new FileDBModel()
                    {
                        CheckCode = "",
                        CurrentName = request.Token,
                        CurrentPointName = _currentPointName ?? "",
                        FID = 0,
                        MimeType = request.ContentType,
                        FileSavePath = "TempFolder",
                        OriginalName = request.FileName,
                        Size = request.FileSize,
                        Type = request.ContentType
                    };
                }
                File.Delete(lockFilePath);
                return true;
            }


        }


        /// <summary>
        /// 根据校验码搜索
        /// </summary>
        /// <param name="checkCode"></param>
        /// <returns></returns>
        public List<FileDBModel> SearchByCheckCode(string checkCode)
        {
            if (GCCEntrance.DBManager is null || GCCEntrance.DBManager.LocalDB is null)
            {
                _logger?.Error("Local db instance was null.");
                return new List<FileDBModel>();
            }

            return GCCEntrance.DBManager.LocalDB.Select<FileDBModel>().Where(item => item.CheckCode == checkCode).ToList();
        }

        /// <summary>
        /// 检索所有项目
        /// </summary>
        /// <param name="count"></param>
        /// <returns></returns>
        public List<FileDBModel> SearchItems(PageRequestBase pageInfo, out long count)
        {
            count = 0;
            if (GCCEntrance.DBManager is null || GCCEntrance.DBManager.LocalDB is null)
            {
                _logger?.Error("Local db instance was null.");
                return new List<FileDBModel>();
            }
            return GCCEntrance.DBManager.LocalDB.Select<FileDBModel>().Count(out count)
                .Page(pageInfo.PageIndex ?? 0, pageInfo.PageSize)
                .ToList();
        }


        /// <summary>
        /// 根据ID检索
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public FileDBModel? SearchByID(long id)
        {
            if (GCCEntrance.DBManager is null || GCCEntrance.DBManager.LocalDB is null)
            {
                _logger?.Error("Local db instance was null.");
                return default;
            }
            return GCCEntrance.DBManager.LocalDB.Select<FileDBModel>().Where(item => item.FID == id).First();
        }




        /// <summary>
        /// 计算文件sha256 如果你们愿意,可以再做一个core库,但是现阶段完全没有必要
        /// </summary>
        /// <param name="filePath">文件地址</param>
        /// <returns></returns>
        private static string SHA256Encrypt(Stream stream)
        {
            using var sha256 = SHA256.Create();
            stream.Position = 0;
            byte[] hashValue = sha256.ComputeHash(stream);
            return PrintByteArray(hashValue);
        }





        /// <summary>
        /// 以可读格式显示字节数组
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        private static string PrintByteArray(byte[] array)
        {
            var sb = new StringBuilder();
            for (int i = 0; i < array.Length; i++)
            {
                _ = sb.Append($"{array[i]:X2}");
            }
            return sb.ToString();
        }

    }
}
